package uk.ac.strath.cis.collections;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * An interface for Trees
 * 
 * @author Robert
 * 
 * @param <T>
 */
public interface ITree<T extends ITree<T>> {

    /**
     * 
     * @param nodeName
     */
    public void setNodeName(String nodeName);

    /**
     * 
     * @return
     */
    public String getNodeName();

    /**
     * 
     * @param child
     * @return
     */
    public boolean addChild(T child);

    /**
     * 
     * @return
     */
    public List<T> getChildren();

    /**
     * 
     * @param n
     * @return
     */
    public T getNthChild(int n);

    /**
     * 
     * @return
     */
    public boolean isLeaf();
    
    public static class Tree implements ITree<Tree> {

        protected final List<Tree> children = new ArrayList<Tree>();
        private StringBuilder buffer;
        private String nodeName;

        /**
         * Sets the current node's name
         */
        public void setNodeName(String nodeName) {
            this.nodeName = nodeName;
        }

        /**
         * returns the current node's name
         */
        public String getNodeName() {
            return nodeName;
        }

        /**
         * Adds a node as a child of this node
         */
        public boolean addChild(Tree child) {
            return children.add(child);
        }

        /**
         * Adds a node as a child of this node
         * @param childName
         * @return
         */
        public boolean addChild(String childName) {
            Tree child = new Tree();
            child.setNodeName(childName);
            return addChild(child);
        }

        /**
         * returns the child nodes of this node
         */
        public List<Tree> getChildren() {
            return children;
        }

        /**
         * returns the nth child of this node
         */
        public Tree getNthChild(int n) {
            return children.get(n);
        }

        /**
         * check whether the current node is a leaf
         */
        public boolean isLeaf() {
            return children.size() == 0;
        }

        /**
         * returns a string representation of this tree
         */
        @Override
        public String toString() {
            buffer = new StringBuilder();
            printTree(this);
            final String s = buffer.toString();
            buffer = null;
            return s;
        }

        private void printTree(Tree tree) {
            if (tree.isLeaf()) {
                buffer.append(tree.getNodeName());
            } else {
                buffer.append(tree.getNodeName());
                buffer.append("(");

                int i = 0;

                final Iterator<Tree> child = tree.getChildren()
                        .iterator();
                while (i < tree.getChildren().size() - 1) {

                    printTree(child.next());
                    buffer.append(", ");
                    i++;
                }
                printTree(child.next());
                buffer.append(")");

            }
        }

        @Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (!(obj instanceof Tree)) {
				return false;
			}
			Tree other = (Tree) obj;
			if (children == null) {
				if (other.children != null) {
					return false;
				}
			} else if (!children.equals(other.children)) {
				return false;
			}
			if (nodeName == null) {
				if (other.nodeName != null) {
					return false;
				}
			} else if (!nodeName.equals(other.nodeName)) {
				return false;
			}
			return true;
		}
        
        @Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result
					+ ((children == null) ? 0 : children.hashCode());
			result = prime * result
					+ ((nodeName == null) ? 0 : nodeName.hashCode());
			return result;
		}
    }

}
